<template>
  <div class="fx-select" :class="{active:isFocus,disabled,clear}" :style="{width:`${$attrs.width}px`}"
       ref="selectWrapperDom" @click.stop="selectClick">
    <input type="text" :class="[size,{multiple}]"
           v-model="selectLabel" v-bind="$attrs"
           :readonly="selectInputReadonly" :disabled="disabled"
           @focus="selectFocus" @blur="selectBlur">
    <div class="select-icon" :class="{active:modalShow}">
      <span class="fx-select-dropdown" :class="{active:modalShow}"/>
      <i class="select-clear iconfont icon-multiply_circle fs12" @click.stop="clearClick"/>
    </div>
    <div class="fx-select-multiple" :class="[size]" v-if="multiple">
      <span v-for="item in selectLabel" :key="item.value" class="text_hide" :class="{collapse}">
        {{ item.label }}
        <i class="iconfont icon-multiply_circle fs12 ml5"
           @click.stop="multipleClearClick(item.value)"/>
      </span>
      <em v-if="collapseNumber">+ {{ collapseNumber }}</em>
    </div>
    <div class="select-content" ref="selectContentDom">
      <transition @before-enter="selectBeforeEnterOrLeave" @enter="selectEnterOrBeforeLeave"
                  @before-leave="selectEnterOrBeforeLeave"
                  @leave="selectBeforeEnterOrLeave">
        <div class="select-content-arrow" v-show="modalShow">
          <ul>
            <slot></slot>
          </ul>
        </div>
      </transition>
    </div>
  </div>
</template>

<script lang="ts">
import {
  defineComponent,
  PropType,
  ref,
  SetupContext,
  provide,
  computed,
  watch,
  onBeforeMount,
  onUnmounted
} from 'vue'
import { ComponentSize, componentSizeArr } from '@/base/types'
import mitt, { Emitter } from 'mitt'
import { getZIndex, useClickBlank, useModelWatch } from '@/base/hooks'
import { addClassName, removeClassName } from '@/base/utils'

export type ModelValueProps = string | number | Array<string | number>
export type SelectData = { value: string | number, label: string | number }

interface SelectProps {
  modelValue: ModelValueProps,
  size: ComponentSize,
  multiple: boolean,
  collapse: boolean
  disabled: boolean,
  clear: boolean,
}

export default defineComponent({
  name: 'FSelect',
  props: {
    modelValue: {
      type: [String, Number, Array] as PropType<ModelValueProps>,
      default: '',
      required: true
    },
    // 大小
    size: {
      type: String as PropType<ComponentSize>,
      default: 'default',
      validator (val: ComponentSize): boolean {
        return componentSizeArr.includes(val)
      }
    },
    // 多选
    multiple: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 多选时 显示一个tag
    collapse: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 禁用
    disabled: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 是否清空
    clear: {
      type: Boolean as PropType<boolean>,
      default: false
    }
  },
  emits: ['update:modelValue', 'focus', 'blur', 'change'],
  setup (props: SelectProps, ctx: SetupContext) {
    // 是否显示下拉框
    const modalShow = ref<boolean>(false)
    // select选中框状态
    const selectInputReadonly = ref<boolean>(true)
    // 当前选中
    const selectValue = ref<ModelValueProps>(props.modelValue)
    // 当前选中显示文字
    const selectLabel = computed(() => {
      if (selectValue.value instanceof Array) {
        const arr: SelectData[] = []
        selectValue.value.forEach((item: string | number) => {
          const has = selectData.value.find((child: SelectData) => child.value === item)
          if (has) arr.push(has)
        })
        return props.collapse ? arr.length ? [arr[0]] : [] : arr
      } else {
        const index = selectData.value.findIndex((item: SelectData) => item.value === selectValue.value)
        return index < 0 ? '' : selectData.value[index].label
      }
    })
    // 所有下拉选择数据
    const selectData = ref<SelectData[]>([])
    // 是否聚焦
    const isFocus = ref<boolean>(false)
    // selectInput Dom
    const selectWrapperDom = ref<HTMLElement | undefined>(undefined)
    // selectContent Dom
    const selectContentDom = ref<HTMLElement | undefined>(undefined)
    // 数据监听
    const selectMitt: Emitter = mitt()
    // 是否显示多选个数
    const collapseNumber = computed(() => {
      if (props.collapse && selectValue.value instanceof Array) return selectValue.value.length
      return 0
    })

    provide('selectValue', computed(() => selectValue.value))
    provide('selectMittEmit', selectMitt.emit)

    // 数据双向绑定hook
    useModelWatch<ModelValueProps>(() => props.modelValue, selectValue)
    // 处理点击空白隐藏hook
    useClickBlank(selectWrapperDom, modalShow)

    watch(modalShow, (val: boolean): void => {
      if (!val) {
        document.removeEventListener('scroll', handlePosition, true)
      } else {
        document.addEventListener('scroll', handlePosition, true)
      }
    })

    // select组件聚焦
    const selectFocus = (ev: Event): void => {
      isFocus.value = true
      ctx.emit('focus', ev)
    }
    // select组件失焦
    const selectBlur = (ev: Event): void => {
      isFocus.value = false
      ctx.emit('blur', ev)
    }
    // 处理下拉框定位
    const handlePosition = (): void => {
      if (!selectWrapperDom.value || !selectContentDom.value) return
      const rect = selectWrapperDom.value.getBoundingClientRect()
      const height = selectData.value.length * 34
      const contentHeight = height > 200 ? 200 : height
      const { clientHeight } = document.documentElement
      selectContentDom.value.style.width = `${rect.width}px`
      selectContentDom.value.style.zIndex = `${getZIndex()}`
      selectContentDom.value.style.left = `${rect.left}px`
      selectContentDom.value.style.top = `${rect.top + rect.height}px`
      if ((rect.top + rect.height + contentHeight) >= clientHeight) {
        selectContentDom.value.style.top = `${rect.top - contentHeight - 20}px`
      }
    }
    // 监听option点击
    const handleSelectMitt = (val: SelectData | undefined): void => {
      if (!val) return
      if (props.multiple && selectValue.value instanceof Array) {
        const index = selectValue.value.findIndex((item: string | number) => val.value === item)
        index < 0 ? selectValue.value.push(val.value) : selectValue.value.splice(index, 1)
      } else {
        selectValue.value = val.value
        modalShow.value = false
      }
    }
    // 监听option数据返回 整理所有数据
    const handleSelectDataMitt = (val: SelectData | undefined): void => {
      if (!val) return
      selectData.value.push(val)
    }
    // 选项点击
    const selectClick = (): void => {
      if (props.disabled) return
      modalShow.value = !modalShow.value
    }
    // 清空
    const clearClick = (): void => {
      selectValue.value = props.multiple ? [] : ''
    }
    // 多选框 选择删除
    const multipleClearClick = (val: string | number): void => {
      if (props.multiple && selectValue.value instanceof Array) {
        const index = selectValue.value.findIndex((item: string | number) => item === val)
        selectValue.value.splice(index, 1)
      }
    }

    // 动画进入前
    const selectBeforeEnterOrLeave = (el: HTMLElement) => {
      if (!selectWrapperDom.value) return
      const rect = selectWrapperDom.value.getBoundingClientRect()
      const height = el.children[0].children.length * 34
      const contentHeight = height > 200 ? 200 : height
      const { clientHeight } = document.documentElement
      const isBottom = (rect.top + rect.height + contentHeight) >= clientHeight
      isBottom ? addClassName(el, 'top') : removeClassName(el, 'top')
      el.style.transform = isBottom ? 'translateY(100%)' : 'translateY(-100%)'
    }
    // 动画进入
    const selectEnterOrBeforeLeave = (el: HTMLElement) => {
      handlePosition()
      el.style.transform = 'translateY(0)'
    }

    onBeforeMount(() => {
      selectMitt.on<SelectData | undefined>('selectMitt', handleSelectMitt)
      selectMitt.on<SelectData | undefined>('selectDataMitt', handleSelectDataMitt)
    })

    onUnmounted(() => {
      selectMitt.off('selectMitt', handleSelectMitt)
      selectMitt.off('selectDataMitt', handleSelectDataMitt)
    })

    return {
      modalShow,
      selectInputReadonly,
      selectWrapperDom,
      selectContentDom,
      selectValue,
      selectLabel,
      isFocus,
      collapseNumber,
      selectFocus,
      selectBlur,
      handlePosition,
      selectClick,
      clearClick,
      selectBeforeEnterOrLeave,
      selectEnterOrBeforeLeave,
      multipleClearClick
    }
  }
})
</script>
